/*
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/

#pragma once

#include <systemd/sd-journal.h>
#include <unistd.h>

#include <PSUInfo.hpp>
#include <Utils.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/streambuf.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <gpiod.hpp>
#include <sdbusplus/asio/object_server.hpp>

#include <filesystem>
#include <fstream>
#include <iostream>
#include <istream>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <regex>
#include <set>
#include <stdexcept>
#include <string>
#include <utility>
#include <variant>
#include <vector>

#define REDUNDANCY_LOST (0x01)
#define PSU_MISMATCH (0x06)

#define STATUS_OCP (0x80)
#define STATUS_OVP (0x80)
#define STATUS_UVP (0x10)
#define STATUS_OTP (0x80)
#define STATUS_ASSERT (0x80)
#define STATUS_DEASSERT (0x00)

#define STATUS_NORMAL (0x0000)
#define OUTPUT_OVER_VOLT_PROTECTION (0x8860)
#define OUTPUT_UNDER_VOLT_PROTECTION (0x8840)
#define OVER_TEMP_WARNING (0x0004)
#define OVER_TEMP_PROTECTION (0x0844)
#define OUTPUT_OVER_CURRENT_POWER_WARNING (0x4000)
#define OUTPUT_OVER_CURRENT_PROTECTION (0x4850)
#define FAN_WARNING (0x0400)
#define FAN_FAULT (0x0C40)
#define INPUT_WARNING (0x2000)
#define INPUT_UNDER_VOLT_PROTECTION (0x2848)
#define INPUT_OVER_VOLT_PROTECTION (0x2840)
#define POWER_SUPPLY_OFF (0x0840)

#define PRESENCE_DETECTED (0x00)
#define FAILURE_DETECTED (0x01)
#define PREDICTIVE_FAILURE (0x02)
#define INPUT_LOST (0x03)
#define INPUT_LOST_OR_OUT_OF_RANGE (0x04)
#define PSU_GPIO_LOW (0)
#define PSU_GPIO_HIGH (1)

#define PSU_OIS_STATUS_COUNT (3)

#define PSU_CTRL_BY_COMMAND (0x48)
#define PSU_TURN_OFF (0x40)

#define MASK_BIT_PSU_STATUS_WORD_VIN_UV_FAULT                                  \
    (0x0008) // Status word low byte bit3: VIN_UV_FAULT: An input under voltag
             // fault has occurred

#define PSU_STATUS_VOUT_FAULT_MASK (0x8020)
#define PSU_STATUS_INPUT_FAULT_MASK (0x2008)
#define PSU_STATUS_IOUT_FAULT_MASK (0x4010)
#define PSU_STATUS_TEMPERATURE_FAULT_MASK (0x0004)
#define PSU_STATUS_FAN_FAULT_MASK (0x0400)

#define PSU_STATUS_VOUT_FAULT_REGISTER_SUPPLY_FAIL (0x90)
#define PSU_STATUS_INPUT_FAULT_REGISTER_SUPPLY_FAIL (0x80)
#define PSU_STATUS_INPUT_FAULT_REGISTER_PREDICTIVE_FAIL (0x23)
#define PSU_STATUS_INPUT_FAULT_REGISTER_AC_LOST (0x18)
#define PSU_STATUS_IOUT_FAULT_REGISTER_SUPPLY_FAIL (0x82)
#define PSU_STATUS_IOUT_FAULT_REGISTER_PREDICTIVE_FAIL (0x21)
#define PSU_STATUS_TEMPERATURE_FAULT_REGISTER_SUPPLY_FAIL (0x80)
#define PSU_STATUS_TEMPERATURE_FAULT_REGISTER_PREDICTIVE_FAIL (0x40)
#define PSU_STATUS_FAN_FAULT_REGISTER_SUPPLY_FAIL (0x80)
#define PSU_STATUS_FAN_FAULT_REGISTER_PREDICTIVE_FAIL (0x28)

class PSUPresent
{
  public:
  public:
    PSUPresent(boost::asio::io_context& io,
               sdbusplus::asio::object_server& objectServer,
               std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
               const ManagedObjectType& sensorConfigs,
               const std::shared_ptr<boost::container::flat_set<std::string>>&
                   sensorsChanged,
               const std::string& psuName, const std::string& gpioName, const std::string& gpioMode);
    ~PSUPresent();

    void monitorPresence(void);
    void monitorPresencePoll(void);
    void read(void);
    bool getValue(void) const;
    void updatePresent(void);

  private:
    boost::asio::io_context& io;
    sdbusplus::asio::object_server& objServer;
    std::shared_ptr<sdbusplus::asio::connection> conn;
    ManagedObjectType sensorConfigs;
    std::shared_ptr<boost::container::flat_set<std::string>> sensorsChanged;
    std::string psuName;
    std::string gpioName;
    std::string gpioMode;
    bool status = true;
    bool gpiovalue = true;
    gpiod::line gpioLine;
    boost::asio::posix::stream_descriptor gpioFd;
    std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
    boost::asio::deadline_timer waitTimer;
    static constexpr unsigned int gpioPollTimeMs = 1000;
};

struct PsuStatus
{
    uint16_t ipmierror;
    std::string psustatusmessage;
};
static std::map<uint16_t, PsuStatus> statusWordMap = {
    {STATUS_NORMAL, {PRESENCE_DETECTED, "PRESENCE_DETECTED"}},
    {OUTPUT_OVER_VOLT_PROTECTION,
     {FAILURE_DETECTED, "Output Over Voltage Protection"}},
    {OUTPUT_UNDER_VOLT_PROTECTION,
     {FAILURE_DETECTED, "Output Under Voltage Protection"}},
    {OVER_TEMP_PROTECTION, {FAILURE_DETECTED, "Over Temperature Protection"}},
    {OUTPUT_OVER_CURRENT_PROTECTION,
     {FAILURE_DETECTED, "Output Over Current or Power Protection"}},
    {FAN_FAULT, {FAILURE_DETECTED, "Fan Fault"}},
    {INPUT_OVER_VOLT_PROTECTION,
     {FAILURE_DETECTED, "Input Over Voltage Protection"}},
    {FAN_WARNING, {PREDICTIVE_FAILURE, "Fan Warning"}},
    {OVER_TEMP_WARNING, {PREDICTIVE_FAILURE, "Over Temperature Warning"}},
    {OUTPUT_OVER_CURRENT_POWER_WARNING,
     {PREDICTIVE_FAILURE, "Output Over Current or Power Warning"}},
    {INPUT_WARNING, {PREDICTIVE_FAILURE, "Input OC OP UV Warning"}},
    {INPUT_UNDER_VOLT_PROTECTION,
     {INPUT_LOST_OR_OUT_OF_RANGE, "Input Under Voltage Protection"}},
};

/* struct IOut_PsuStatus
{
    uint16_t IOut_ipmierror;
    std::string IOut_psustatusmessage;
}; */

static std::map<uint8_t, std::string> IOut_statusByteMap = {
    {STATUS_OCP, "Output overcurrentfault"},
};

class IOut_Status
{
  public:
    IOut_Status(
        const std::string& bus, const std::string& address,
        sdbusplus::asio::object_server& objectServer,
        const std::string& psuName, boost::asio::io_service& io,
        std::map<std::string, std::string> psuPresentGpio,
        boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
            allPSUInfos);

    ~IOut_Status();
    void IOut_setupRead(void);
    void IOut_getStatusByte(void);
    void IOut_updateStatusByte(void);
    uint8_t getValue(void) const;

    std::shared_ptr<sdbusplus::asio::dbus_interface> IOut_psustatusInterface;
    bool IOut_Present = true;
    size_t IOut_StatusByte = 0;
    boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
        allPSUInfos;

  private:
    sdbusplus::asio::object_server& objServer;
    std::string psuName;
    boost::asio::deadline_timer waitTimer;

    std::string IOut_StatusBytePath;
    std::string IOut_psuStatus;
    uint8_t IOut_LastStatusByte = 0;
    void IOut_handleResponse(void);
    static constexpr unsigned int pollMs = 2000;
    std::vector<uint8_t> IOut_statusByteOis;
    uint32_t IOut_statusByteOisIndex = 0;
    std::map<std::string, std::string> psuPresentGpio;
    boost::asio::posix::stream_descriptor gpioFd;
};

/* struct VOut_PsuStatus
{
        uint16_t VOut_ipmierror;
        std::string VOut_psustatusmessage;
}; */

static std::map<uint8_t, std::string> VOut_statusByteMap = {
    {STATUS_OVP, "Output Overvoltage Fault"},
    {STATUS_UVP, "Output Undervoltage Fault"},
};

class VOut_Status
{
  public:
    VOut_Status(
        const std::string& bus, const std::string& address,
        sdbusplus::asio::object_server& objectServer,
        const std::string& psuName, boost::asio::io_service& io,
        std::map<std::string, std::string> psuPresentGpio,
        boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
            allPSUInfos);

    ~VOut_Status();
    void VOut_setupRead(void);
    void VOut_getStatusByte(void);
    void VOut_updateStatusByte(void);
    uint8_t getValue(void) const;

    std::shared_ptr<sdbusplus::asio::dbus_interface> VOut_psustatusInterface;
    bool VOut_Present = true;
    size_t VOut_StatusByte = 0;
    boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
        allPSUInfos;

  private:
    sdbusplus::asio::object_server& objServer;
    std::string psuName;
    boost::asio::deadline_timer waitTimer;

    std::string VOut_StatusBytePath;
    std::string VOut_psuStatus;
    uint8_t VOut_LastStatusByte = 0;
    void VOut_handleResponse(void);
    static constexpr unsigned int pollMs = 2000;
    std::vector<uint8_t> VOut_statusByteOis;
    uint32_t VOut_statusByteOisIndex = 0;
    std::map<std::string, std::string> psuPresentGpio;
    boost::asio::posix::stream_descriptor gpioFd;
};

/* struct Input_PsuStatus
{
        uint16_t Input_ipmierror;
        std::string Input_psustatusmessage;
};
 */
static std::map<uint8_t, std::string> Input_statusByteMap = {
    {STATUS_OVP, "Input Overvoltage Fault"},
    {STATUS_UVP, "Input Undervoltage Fault"},
};

class Input_Status
{
  public:
    Input_Status(
        const std::string& bus, const std::string& address,
        sdbusplus::asio::object_server& objectServer,
        const std::string& psuName, boost::asio::io_service& io,
        std::map<std::string, std::string> psuPresentGpio,
        boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
            allPSUInfos);

    ~Input_Status();
    void Input_setupRead(void);
    void Input_getStatusByte(void);
    void Input_updateStatusByte(void);
    uint8_t getValue(void) const;

    std::shared_ptr<sdbusplus::asio::dbus_interface> Input_psustatusInterface;
    bool Input_Present = true;
    size_t Input_StatusByte = 0;
    boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
        allPSUInfos;

  private:
    sdbusplus::asio::object_server& objServer;
    std::string psuName;
    boost::asio::deadline_timer waitTimer;

    std::string Input_StatusBytePath;
    std::string Input_psuStatus;
    uint8_t Input_LastStatusByte = 0;
    void Input_handleResponse(void);
    static constexpr unsigned int pollMs = 2000;
    std::vector<uint8_t> Input_statusByteOis;
    uint32_t Input_statusByteOisIndex = 0;
    std::map<std::string, std::string> psuPresentGpio;
    boost::asio::posix::stream_descriptor gpioFd;
};

/* struct OTP_PsuStatus
{
        uint16_t OTP_ipmierror;
        std::string OTP_psustatusmessage;
}; */

static std::map<uint8_t, std::string> OTP_statusByteMap = {
    {STATUS_OTP, "Over Temperature Fault"},

};

class OTP_Status
{
  public:
    OTP_Status(const std::string& bus, const std::string& address,
               sdbusplus::asio::object_server& objectServer,
               const std::string& psuName, boost::asio::io_service& io,
               std::map<std::string, std::string> psuPresentGpio,
               boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
                   allPSUInfos);

    ~OTP_Status();
    void OTP_setupRead(void);
    void OTP_getStatusByte(void);
    void OTP_updateStatusByte(void);
    uint8_t getValue(void) const;

    std::shared_ptr<sdbusplus::asio::dbus_interface> OTP_psustatusInterface;
    bool OTP_Present = true;
    size_t OTP_StatusByte = 0;
    boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
        allPSUInfos;

  private:
    sdbusplus::asio::object_server& objServer;
    std::string psuName;
    boost::asio::deadline_timer waitTimer;

    std::string OTP_StatusBytePath;
    std::string OTP_psuStatus;
    uint8_t OTP_LastStatusByte = 0;
    void OTP_handleResponse(void);
    static constexpr unsigned int pollMs = 2000;
    std::vector<uint8_t> OTP_statusByteOis;
    uint32_t OTP_statusByteOisIndex = 0;
    std::map<std::string, std::string> psuPresentGpio;
    boost::asio::posix::stream_descriptor gpioFd;
};

static std::map<uint8_t, std::string> Fan12_statusByteMap = {
    {PSU_STATUS_FAN_FAULT_REGISTER_SUPPLY_FAIL, "fan fault"},
    {PSU_STATUS_FAN_FAULT_REGISTER_PREDICTIVE_FAIL, "fan waring"}};

class Fan12_Status
{
  public:
    Fan12_Status(
        const std::string& bus, const std::string& address,
        sdbusplus::asio::object_server& objectServer,
        const std::string& psuName, boost::asio::io_service& io,
        std::map<std::string, std::string> psuPresentGpio,
        boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
            allPSUInfos);

    ~Fan12_Status();
    void Fan12_setupRead(void);
    void Fan12_getStatusByte(void);
    void Fan12_updateStatusByte(void);
    uint8_t getValue(void) const;

    std::shared_ptr<sdbusplus::asio::dbus_interface> Fan12_psustatusInterface;
    bool Fan12_Present = true;
    size_t Fan12_StatusByte = 0;
    boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
        allPSUInfos;

  private:
    sdbusplus::asio::object_server& objServer;
    std::string psuName;
    boost::asio::deadline_timer waitTimer;

    std::string Fan12_StatusBytePath;
    std::string Fan12_psuStatus;
    uint8_t Fan12_LastStatusByte = 0;
    void Fan12_handleResponse(void);
    static constexpr unsigned int pollMs = 2000;
    std::vector<uint8_t> Fan12_statusByteOis;
    uint32_t Fan12_statusByteOisIndex = 0;
    std::map<std::string, std::string> psuPresentGpio;
    boost::asio::posix::stream_descriptor gpioFd;
};

class PSUStatus
{
  public:
    PSUStatus(
        const std::string& bus, const std::string& address,
        sdbusplus::asio::object_server& objectServer,
        const std::string& psuName, boost::asio::io_service& io,
        boost::container::flat_map<std::string, std::shared_ptr<PSUPresent>>
            psuPresent,
        std::shared_ptr<PSUInfo>& psuinfo,
        boost::container::flat_map<std::string, std::shared_ptr<IOut_Status>>
            psuIoutStatus,
        boost::container::flat_map<std::string, std::shared_ptr<VOut_Status>>
            psuVoutStatus,
        boost::container::flat_map<std::string, std::shared_ptr<Input_Status>>
            psuInputStatus,
        boost::container::flat_map<std::string, std::shared_ptr<OTP_Status>>
            psuOtpStatus,
        boost::container::flat_map<std::string, std::shared_ptr<Fan12_Status>>
            psuFan12Status);
    ~PSUStatus();

    void setupRead(void);
    void getPsuPresent(void);
    void getStatusWord(void);
    void updateStatusWord(void);
    void monitorPresence(void);
    void addPsuWorkMode2DBus(const std::string& filename);
    void addPsuInputType2DBus(const std::string& filename);
    void updateBlackbox(void);
    void updateBlackboxRecord(void);

    std::shared_ptr<sdbusplus::asio::dbus_interface> psustatusInterface;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psustatussensorinterface;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psustatusDiagIntf;
    bool Present = true;
    bool lastPresent = false;
    uint16_t StatusWord = 0;

  private:
    sdbusplus::asio::object_server& objServer;
    std::string psuName;
    boost::asio::deadline_timer waitTimer;
    gpiod::line gpioLine;
    std::string StatusWordPath;
    std::string psuStatus;
    uint16_t LastStatusWord = 0;
    void handleResponse(void);
    static constexpr unsigned int pollMs = 2000;
    std::vector<uint16_t> statusWordOis; // 用于statusWord防抖
    uint32_t statusWordOisIndex = 0;     // 用于statusWord防抖
    std::shared_ptr<sdbusplus::asio::dbus_interface> psuModeInterface;
    std::string psuWorkMode;
    std::string crmodePath;
    std::string onoffconfPath;
    std::string operationPath;
    boost::container::flat_map<std::string, std::shared_ptr<PSUPresent>>
        psuPresent;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psuInputTypeIntface;
    std::shared_ptr<PSUInfo>& psuinfo;
    boost::container::flat_map<std::string, std::shared_ptr<IOut_Status>>
        psuIoutStatus;
    boost::container::flat_map<std::string, std::shared_ptr<VOut_Status>>
        psuVoutStatus;
    boost::container::flat_map<std::string, std::shared_ptr<Input_Status>>
        psuInputStatus;
    boost::container::flat_map<std::string, std::shared_ptr<OTP_Status>>
        psuOtpStatus;
    boost::container::flat_map<std::string, std::shared_ptr<Fan12_Status>>
        psuFan12Status;
    // used for read blackbox
    bool blackboxGot = false;
    std::string blackboxReadPath;
};

class PSUCombineStatus
{
  public:
    PSUCombineStatus(
        boost::asio::io_service& io,
        sdbusplus::asio::object_server& objectServer,
        boost::container::flat_map<std::string, std::unique_ptr<PSUStatus>>
            psuStatus,
        std::map<std::string, std::string> psuPresentGpio,
        boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
            allPSUInfos);
    ~PSUCombineStatus();

    void setupRead(void);
    bool checkPresentRedundantLost(void);
    bool checkACRedundantLost(void);
    void updateRedundant(void);
    bool checkMismatch(void);
    void updateMismatch(void);
    double calculationPsuAllRatedPower(void);

    boost::container::flat_map<std::string, std::unique_ptr<PSUStatus>>
        psuStatus;
    boost::container::flat_map<std::string, std::shared_ptr<PSUInfo>>
        allPSUInfos;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psuMismatchInterface;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psuMismatchSensorInterface;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psuRedSensorInterface;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psuMismatchDiagIntf;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psuLostInterface;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psuLostDiagIntf;
    std::shared_ptr<sdbusplus::asio::dbus_interface> psuCapacityWattsInterface;

  private:
    sdbusplus::asio::object_server& objServer;
    std::map<std::string, bool> LastPresent;
    std::map<std::string, uint16_t> lastStatusWord;
    boost::asio::deadline_timer waitTimer;
    std::map<std::string, std::string> psuPresentGpio;
    static constexpr unsigned int psuPollMs = 2000;
};

class VR_Version
{
  public:
    VR_Version(const std::string& bus, const std::string& address,
               sdbusplus::asio::object_server& objectServer,
               const std::string& vrName);
    ~VR_Version();
    void setupRead(void);
    std::string getMotherBoardPartNumber(void);
    std::shared_ptr<sdbusplus::asio::dbus_interface> VRSoftVersionInterface;

  private:
    sdbusplus::asio::object_server& objServer;
    std::string vrName;
    std::string hwMonitorPath;
};